// C backend for comun, this one should be superior to the older C transpiler,
// this even uses some cool tricks to work in a single pass, all code will be
// kept as close as possible to C89 but sometimes C99 features may be used by
// default (e.g. stdint.h), it should be easy to get rid of them. For each
// -m passed memory for program will be increased.

#include <stdio.h>

#define MIN_STACK_SIZE 64
#define MAX_FORW_FUNCS 64
#define STR_SIZE 32

unsigned long forwFuncs[MAX_FORW_FUNCS]; // recording calls to forward funcs
int forwFuncCount;

char s1[STR_SIZE], s2[STR_SIZE], indentS[STR_SIZE];
unsigned long stackSizes[4];   // stack size estimate for each TE
unsigned long maxPtrAddrs[4];  // to help estimate stack sizes
unsigned int pointerCounts[4]; // number of pointers needed for each TE  
unsigned long addr, line = 1;
int i, tmp, indent, mainParts, memIncrease = 1;
char state = 0;  // 0: fun. ended, 1: out of fun., 2: fun. started, 3: in fun.
char state2 = 0; // 0: nothing, 1: read if, 2: read loop, 3: read goto 
unsigned int instr = 0;         // whole instr.
unsigned short iopcode, iparts; // instr opcode, num. of instr. parts
unsigned long ic, ic1, ic2;     // immediate consts
char ite, itec, inopop, imode;  // type env., type env. char, no-pop bit, mode
char stackAdd, *xStr, xSigC, *yStr, ySigC;

char *numToStr(unsigned int x)
{
  char *s = s1 + STR_SIZE - 1;

  *s = 0;

  do
  {
    s--;
    *s = '0' + (x % 10);
    x /= 10;
  } while (x);

  return s;
}

const char *indS(unsigned int n) { return indentS + STR_SIZE - 1 - 2 * n; }
unsigned int ptrIndex(unsigned int p) { return p < 16 ? 0 : (p - 15); }
int ptrOffset(unsigned int p)         { return p < 16 ? -1 * p : 0; }

void ptrSeen(unsigned int ptrNo,unsigned int *pCount)
{
  if ((ptrIndex(ptrNo) + 1) > *pCount)
    *pCount = ptrIndex(ptrNo) + 1;
}

int main(int argc, char **argv)
{
  for (i = 0; i < argc; ++i)
    if (argv[i][0] == '-' && argv[i][1] == 'm')
      memIncrease++;

  for (i = 0; i < STR_SIZE - 1; ++i)
    indentS[i] = ' ';

  // prologue:
  puts("#include <stdio.h>\n#include <stdint.h>\n\n"
    "#define TE0 unsigned int\n#define TE1 uint8_t\n"
    "#define TE2 uint16_t\n#define TE3 uint32_t\n\n"
    "int  in(void)    { int c = getchar(); return c == EOF ? -1 : c; }\n"
    "void out(char v) { putchar(v); }\n\n"
    "void call(unsigned int f);\n\n"
    "int lastIn = 0;\nint i;\nint ac;\nchar **av;\n\n"
    "TE0  *m0; TE1  *m1; TE2  *m2; TE3  *m3; // memories\n"
    "TE0 **p0; TE1 **p1; TE2 **p2; TE3 **p3; // pointer pointers\n\n"
    "int s0(TE0 x) "
    "{ return (x > (((TE0) -1) / 2)) ? ((int) x) - ((TE0) -1) - 1: x; }\n"
    "TE0 u0(int x) { return x < 0 ? ((TE0) -1) + x + 1 : x; }");

  for (i = 1; i <= 3; ++i)
  {
    tmp = i == 1 ? 10 : (i == 2 ? 1000 : 10000000);
    printf("int s%d(TE%d x) { return (x & 0x%d) ? ((int) x) - 0x%d : x; }\n"
      "TE%d u%d(int x) { return (x < 0) ? 0x%d + x : x; }\n",
      i,i,tmp * 8,tmp * 10,i,i,tmp * 10);
  }

  putchar('\n');

  stackSizes[0] = 1; // we'll always need TE 0 at least for pushing params
  pointerCounts[0] = 1;

  for (i = 0; i < 8; ++i) // skip header
    getchar();

  while (1)
  {
    instr = iopcode = iparts = ic = ite = inopop = 0;

    do
    {
      instr = getchar() | (((unsigned int) getchar()) << 8);

      if (!iparts) // not first?
      {
        iopcode = instr & 0x00ff;
        ite = instr >> 14;
        inopop = (instr >> 13) & 0x01;
      }

      ic |= ((instr >> 8) & 0x0f) << (4 * iparts);
      iparts++;
    } while (instr & 0x1000);

    itec = '0' + ite;

    ic1 = ic & ~(((unsigned long) -1) << ((iparts / 2) * 4));
    ic2 = ic >> ((iparts / 2) * 4);

    if (iopcode == 0x00)
      break; // END

    if (iopcode != 0x01 && iopcode != 0x03) // exclude NOP and COC
    {
      if (state == 0 && !(iopcode == 0x02 && 
        (ic == 7 || ic == 11 || ic == 12 || ic == 13)))
      {
        printf("int main%d(void)\n{\n",mainParts);
        state = 1;
        indent = 1;
        mainParts++;
      }
      else if (state == 2 && iopcode != 0x0c) // 1st fun. instr?
      {
        printf("%lu(void)\n{\n",addr);
        state = 3;
      }

      if (state2 == 2)
      {
        if (iopcode == 0x0b)
          printf("*(p%c[0]%s))\n%s{\n",itec,inopop ? "" : "--",
            indS(indent));
        else
          printf("1)\n%s{\n",indS(indent));

        indent++;
        state2 = 0;
      }
      else if (state2 == 3)
      {
        if (iopcode == 0x0c) // JMA
          printf("%sgoto label%lu;\n",indS(indent),ic); 

        state2 = 0;
      }

      stackSizes[(int) ite] += memIncrease; // we estimate stack size like this

      if (iopcode < 0x20) // special instr?
      {
        switch (iopcode)
        {
          case 0x02: // DES
            switch (ic)
            {
              case 1: // if
                printf("%sif (",indS(indent));
                state2 = 1;
                break;
              case 2: // else
                printf("%s}\n%selse\n%s{\n",indS(indent - 1),
                  indS(indent - 1),indS(indent - 1));
                break;
              case 3: // endif
                indent--;
                printf("%s}\n",indS(indent));
                break;
              case 4: // loop
                printf("%swhile (",indS(indent));
                state2 = 2;
                break;
              case 5: // break
                printf("%sbreak;\n",indS(indent));
                break;
              case 6: // loop end
                indent--;
                printf("%s}\n",indS(indent));
                break;
              case 7: // function
                if (state == 1)
                  puts("  return 1;\n}\n");

                printf("int fun");
                state = 2;
                indent = 1;
                break;
              case 8: printf("%sreturn 0;\n",indS(indent)); break; // exit
              case 9: state2 = 3; break;                           // goto
              case 10: // label
                printf("%slabel%lu:\n",indS(indent),addr + 1);
                break;
              case 11: // new line
                line++;
                printf("// line %lu\n",line);
                break;
              default: break;
            }
            break;

          case 0x07: case 0x08: // CAL CAE
            if (ic > addr) // not defined yet?
            {
              printf("%scall(%lu);\n",indS(indent),ic);
              
              for (i = 0; i < forwFuncCount; ++i)
                if (forwFuncs[i] == ic)
                  break;

              if (i >= forwFuncCount && forwFuncCount < MAX_FORW_FUNCS)
              {
                forwFuncs[forwFuncCount] = ic;
                forwFuncCount++;
              }

            }
            else
              printf("%sfun%s%lu();\n",indS(indent),
                iopcode == 0x08 ? "ext" : "",ic);
            break;

          case 0x09: // RET
            puts("  return 1;\n}\n");
            state = 0;
            break;
          case 0x0b: // JNA
            if (state2 == 1) 
            {
              printf("*(p%c[0]%s))\n%s{\n",
                itec,inopop ? "" : "--",indS(indent));
              state2 = 0;
              indent++;
            }
            break;
          case 0x0f: // INI
            printf("%sfor (i = ac - 1; i >= 0; --i)\n%s{\n"
              "%schar *p = av[i]; while (*p) p++;\n"
              "%swhile (p >= av[i]) { *(++p0[0]) = *p; p--; }\n%s}\n"
              "%s*(++p0[0]) = ac;\n",
              indS(indent),indS(indent),indS(indent + 1),
              indS(indent + 1),indS(indent),indS(indent));

            break;
          case 0x10: // PSC
            printf("%sp%c[%d] = m%c + %lu;\n",indS(indent),
              itec,ptrIndex(ic1),itec,ic2);

            if (ic2 > maxPtrAddrs[ite])
              maxPtrAddrs[ite] = ic2;

            ptrSeen(ic1,pointerCounts + ite);
            break;
          case 0x11: // PAC
            printf("%sp%c[%d] += %d;\n",indS(indent),itec,
              ptrIndex(ic1),(int) (ic2 < 0x08 ? (int) ic2 :
                (((int) ic2) - 16)));

            ptrSeen(ic1,pointerCounts + ite);
            break;
          case 0x12: // PAX
            printf("%sp%c[%d] += s%c(p%c[0][0]);",indS(indent),itec,
              ptrIndex(ic),itec,itec);

            if ((!inopop) && (ic != 0))
              printf(" p%c[0] -= 1;",itec);

            ptrSeen(ic,pointerCounts + ite);

            putchar('\n');
            break;
          case 0x13: // PCO
            printf("%sp%c[%d] = p%c[%d] + %d;\n",indS(indent),itec,
              ptrIndex(ic1),itec,ptrIndex(ic2),
              ptrOffset(ic2));

            ptrSeen(ic1,pointerCounts + ite);
            ptrSeen(ic2,pointerCounts + ite);
            break;
          case 0x14: // MEX
            printf("%sp%c[%d][%d] = p%c[0][0];",indS(indent),itec,
              ptrIndex(ic),ptrOffset(ic),itec);

            if (!inopop)
              printf(" p%c[0]--;",itec);

            ptrSeen(ic,pointerCounts + ite);
            putchar('\n');
            break;
          case 0x15: // MGE
            printf("%sp%c[0][1] = p%c[%d][%d]; p%c[0]++;\n",
              indS(indent),itec,itec,
              ptrIndex(ic),ptrOffset(ic),itec);

            ptrSeen(ic,pointerCounts + ite);
            break;
          case 0x17: // PUX
            printf("%sp%c[0][1] = p%c[0][-1 * ((int) p%c[0][0])]; p%c[0]++;\n",
              indS(indent),itec,itec,itec,itec);
            break;
          case 0x16: // PCM
            printf("%sp%c[0][1] = ((p%c[%d] + %d) != (p%c[%d] + %d)) * "
              "(1 + ((p%c[%d] + %d) < (p%c[%d] + %d))); p%c[0]++;\n",
              indS(indent),itec,itec,ptrIndex(ic2),ptrOffset(ic2),itec,
              ptrIndex(ic1),ptrOffset(ic1),itec,ptrIndex(ic2),ptrOffset(ic2),
              itec,ptrIndex(ic1),ptrOffset(ic1),itec);

            ptrSeen(ic1,pointerCounts + ite);
            ptrSeen(ic2,pointerCounts + ite);
            break;
          case 0x1a: // CON
            printf("%sp%c[0]++; *p%c[0] = %lu;",indS(indent),itec,
              itec,ic);

            if (!inopop)
              printf(" p%c[0]--;",itec);

            if (ic >= ' ' && ic < 127)
              printf(" // '%c'",(char) ic);

            putchar('\n');
            break;
          case 0x1b: // CND
            printf("%sp%c[0][%d] = p%c[0][-2] ? p%c[0][-1] : p%c[0][0]; "
              "p%c[0] += %d;\n",
              indS(indent),itec,inopop ? 1 : -2,itec,
              itec,itec,itec,inopop ? 1 : -2);
            break;
          case 0x1c: // SWP
            if (inopop)
              printf("%sp%c[0][1] = p%c[0][0]; p%c[0][2] = p%c[0][-1];"
                " p%c[0] += 2;\n",indS(indent),itec,itec,
                itec,itec,itec);
            else
              printf("%sp%c[0][0] ^= p%c[0][-1]; p%c[0][-1] ^= p%c[0][0];"
                " p%c[0][0] ^= p%c[0][-1];\n",indS(indent),itec,
                itec,itec,itec,itec,itec);
            break;
          case 0x1d: // TRA
            printf("%sp%c[0][0] = p%c[0][0];",indS(indent),
              '0' + ((char) ic),itec);

            if (!inopop)
              printf(" p%c[0]--;",itec);

            putchar('\n');
            break;
          case 0x1e: // POP
            printf("%sp%c[0] -= %lu;\n",indS(indent),itec,ic + 1);
            break; 
          case 0x1f: // OUT
            printf("%sout(*p%c[0]);",indS(indent),itec);

            if (!inopop)
              printf(" p%c[0]--;",itec);

            putchar('\n');

            break;
          default: break;
        } // switch
      }
      else // typical instrs
      {
        imode = iopcode & 0x03;
        iopcode = iopcode & 0xfc;
        stackAdd = 1;

        if (!inopop)
          stackAdd -= (imode == 0) + (imode != 3);

        printf("%sp%c[0][%d] = ",indS(indent),itec,stackAdd);

        s2[0] = s1[0] = 'p';
        s2[1] = s1[1] = itec;
        s2[2] = s1[2] = '[';
        s2[3] = s1[3] = '0';
        s2[4] = s1[4] = ']';
        s2[5] = s1[5] = '[';
        s2[6] = '-'; s1[6] = '0';
        s2[7] = '1'; s1[7] = ']';
        s2[8] = ']'; s1[8] = 0;
        s2[9] = 0;

        xStr = s1;
        yStr = s2;
        xSigC = ySigC = itec;

        if (imode == 1)
        {
          yStr = xStr;
          xStr = numToStr(ic);
          xSigC = '3';
        }

        switch (iopcode)
        {
          case 0x20: printf("%s + %s",yStr,xStr); break;                // AD
          case 0x24: printf("%s - %s",yStr,xStr); break;                // SU
          case 0x28: printf("%s * %s",yStr,xStr); break;                // MU
          case 0x2c: printf("%s / %s",yStr,xStr); break;                // DI
          case 0x30: printf("u%c(s%c(%s) / s%c(%s))",                   // DS
            itec,ySigC,yStr,xSigC,xStr);
            break;
          case 0x34: printf("%s %% %s",yStr,xStr); break;               // MO
          case 0x38:                                                    // MS
            printf("u%c(s%c(%s) - (s%c(%s) / s%c(%s)) * s%c(%s))",itec,
              ySigC,yStr,ySigC,yStr,xSigC,xStr,xSigC,xStr);
            break;
          case 0x40: case 0x44: case 0x48: case 0x4c:     // GR GE SM SE
            printf("%s %c%s %s",yStr,(iopcode & 0x8) ? '<' : '>',
            (iopcode & 0x04) ? "=" : "",xStr);
            break;
          case 0x50: case 0x54: case 0x58: case 0x5c:     // GS BS SS LS
            printf("s%c(%s) %c%s s%c(%s)",ySigC,yStr,(iopcode & 0x8) ?
            '<' : '>',(iopcode & 0x04) ? "=" : "",xSigC,xStr);
            break;
          case 0x60: printf("%s == %s",yStr,xStr); break;               // EQ
          case 0x64: printf("%s != %s",yStr,xStr); break;               // NE
          case 0x68: printf("%s & %s",yStr,xStr); break;                // BA
          case 0x6c: printf("%s | %s",yStr,xStr); break;                // BO
          case 0x70: printf("%s ^ %s",yStr,xStr); break;                // BX
          case 0x74: printf("%s && %s",yStr,xStr); break;               // LA
          case 0x78: printf("%s || %s",yStr,xStr); break;               // LO
          case 0x7c: printf("(%s == 0) != (%s == 0)",yStr,xStr); break; // LX
          case 0x84: case 0x88:                                         // SR SL
            printf("%s < sizeof(TE%c) * 8 ? %s %s %s : 0",xStr,itec,yStr,
              iopcode & 0x08 ? "<<" : ">>",xStr);
            break;
          case 0xf0: printf("p%c[0] - m%c",itec,itec); break;           // ADR
          case 0xf8: printf("lastIn >= 0"); break;                      // INU
          case 0xfc:                                                    // INP
            printf("lastIn >= 0 ? ((lastIn = in()) >= 0 ? lastIn : 0) : 0"); 
            break;
          default: 
            if ((instr & 0xff) == 0x82)                                   // BN
              printf("~(%s)",xStr);
            break;
        }

        putchar(';');

        if (stackAdd)
          printf(" p%c[0] += %d;",itec,stackAdd);       
 
        putchar('\n');
      }
    }

    addr += iparts;
  }

  if (state == 1)
    puts("  return 1;\n}\n");

  // epilogue:
  puts("void call(unsigned int f)\n{\n  switch (f)\n  {");

  for (i = 0; i < forwFuncCount; ++i)
    printf("    case %lu: fun%lu(); break;\n",forwFuncs[i],forwFuncs[i]);

  puts("    default: break;\n  }\n}\n");

  for (i = 0; i < 4; ++i)
  {
    if (stackSizes[i] && stackSizes[i] <
      ((unsigned int) MIN_STACK_SIZE * memIncrease))
      stackSizes[i] = MIN_STACK_SIZE * memIncrease;

    if (stackSizes[i] && !pointerCounts[i])
      pointerCounts[i] = 1;

    printf("TE%d mem%d[%lu], *ptrs%d[%d];\n",
      i,i,stackSizes[i] + maxPtrAddrs[i],i,pointerCounts[i]);
  }

  puts("\nint main(int argc, char **argv)\n{\n  ac = argc;\n  av = argv;\n");

  for (i = 0; i < 4; ++i)
    if (pointerCounts[i])
      printf("  p%d = ptrs%d;\n  m%d = mem%d;\n\n  for (i = 0; i < %d; ++i)\n"
        "    p%d[i] = m%d;\n\n",i,i,i,i,pointerCounts[i],i,i);

  for (i = 0; i < mainParts; ++i)
    printf("  if (!main%d())\n    return 0;\n\n",i);

  puts("  return 0;\n}");

  return 0;
}
